/*
 *  JVending
 *  Copyright (C) 2006  Shane Isbell
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jvending.provisioning.impl;

/**
 * Provides factory methods for creating objects needed by the provisioning framework.
 *
 * @author Shane Isbell
 * @since 2.0.0
 */

import java.net.*;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import java.io.IOException;
import java.io.InputStream;

import javax.provisioning.*;
import javax.provisioning.adapter.AdapterContext;
import javax.provisioning.adapter.Adapter;
import javax.provisioning.adapter.AdapterException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.JAXBException;

import org.jvending.provisioning.deliverable.BaseDescriptorFile;
import org.jvending.provisioning.deliverable.BaseDeliverable;
import org.jvending.provisioning.stocking.par.ProvisioningArchive;

final class ProvisioningFactory {

    private static Logger logger = Logger.getLogger("ProvisioningFactory");

    private ProvisioningFactory() {
    }

    static FulfillmentTask createFulfillmentTask(BundleDescriptor bundleDescriptor,
                                                 DeliveryContext deliveryContext, String adapterName, long duration,
                                                 ProvisioningContext provisioningContext) {
        return new FulfillmentTaskImpl(bundleDescriptor, deliveryContext, adapterName, duration, provisioningContext);
    }

    static DeliveryContext createDeliveryContext(Capabilities deviceCapabilities,
                                                 String networkID, String user, Map requestMap) {
        return new DeliveryContextImpl(deviceCapabilities, networkID, user, requestMap);
    }

    static Capabilities createEmptyCapabilities() {
        return new EmptyCapabilities();
    }

    static ProvisioningArchive createProvisioningArchive(InputStream is) throws IOException {
        if (is == null) {
            logger.info("JV-1501-012: No provisioning descriptor");
            throw new IOException("JV-1501-012: No provisioning descriptor");
        }
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance("org.jvending.provisioning.stocking.par");
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setValidating(true);
            return (ProvisioningArchive) unmarshaller.unmarshal(is);
        } catch (JAXBException e) {
            logger.log(Level.INFO, "JV-1501-013: Parsing exception for provisioning descriptor", e);
            throw new IOException("JV-1501-013: Parsing exception for provisioning descriptor");
        }
    }

    private static class DeliveryContextImpl implements DeliveryContext {

        private final Capabilities deviceCapabilities;

        private final String networkID;

        private final String user;

        private final Map requestMap;

        DeliveryContextImpl(Capabilities deviceCapabilities,
                            String networkID, String user, Map requestMap) {
            this.networkID = networkID;
            this.deviceCapabilities = deviceCapabilities;
            this.user = user;
            this.requestMap = requestMap;
        }

        public Capabilities getDeviceCapabilities() {
            return deviceCapabilities != null ? deviceCapabilities : new EmptyCapabilities();
        }

        public String getNetworkID() {
            return networkID != null ? networkID : "UNKNOWN";
        }

        public String getUser() {
            return user != null ? user : "UNKNOWN";
        }

        public String toString() {
            return "Delivery Context: Network Id = " + networkID +
                    ", User = " + user +
                    ", Headers:\r\n" + requestMap.toString();
        }
    }

    private static class EmptyCapabilities implements Capabilities {

        //List of Strings: Values of capability
        public List getCapability(String name) {
            return Collections.unmodifiableList(Collections.EMPTY_LIST);
        }

        public Set getCapabilityNames() {
            return Collections.unmodifiableSet(Collections.EMPTY_SET);
        }
    }

    private static class FulfillmentTaskImpl implements FulfillmentTask {

        private BundleDescriptor bundleDescriptor;

        private final DeliveryContext deliveryContext;

        private final String adapterName;

        private final long duration;

        private final String fulfillmentID;

        private final long expiryTime;

        private final ProvisioningContext provisioningContext;

        FulfillmentTaskImpl(BundleDescriptor bundleDescriptor,
                            DeliveryContext deliveryContext, String adapterName, long duration,
                            ProvisioningContext provisioningContext) {

            this.bundleDescriptor = bundleDescriptor;
            this.deliveryContext = deliveryContext;
            this.adapterName = adapterName;
            this.duration = duration;
            expiryTime = System.currentTimeMillis() + duration;
            fulfillmentID = UUID.randomUUID().toString();
            this.provisioningContext = provisioningContext;
        }

        public String getAdapterName() {
            return adapterName;
        }

        public BundleDescriptor getBundleDescriptor() {
            return bundleDescriptor;
        }

        public String getDeliveryURI() throws ProvisioningException {

            URI bundleURI = null;

            BaseDescriptorFile descriptorFile = (BaseDescriptorFile) bundleDescriptor.getDescriptorFile();
            BaseDeliverable contentFile = (BaseDeliverable) bundleDescriptor.getContentFile();
            if (descriptorFile != null) {
                bundleURI = descriptorFile.getURI();
            } else if (contentFile != null) {
                bundleURI = contentFile.getURI();
            }

            //sanity check: should not happen, let null pointer propagate up
            if (bundleURI == null)
                logger.warning("Bundle URI is null. Problem with the data: Bundle Id = " +
                        bundleDescriptor.getBundleID());

            if (bundleURI.isAbsolute())//handled by some other server
                return bundleURI.toString();

            AdapterContext adapterContext = (AdapterContext) provisioningContext;
            Adapter adapter = adapterContext.getAdapter(adapterName);
            try {
                return adapter.createFulfillmentURI(this);
            } catch (AdapterException e) {
                throw new ProvisioningException("JV: Could not create Fulfillment URI", e);
            }
        }

        public DeliveryContext getDeliveryContext() {
            return deliveryContext;
        }

        public long getExpiryTime() {
            return expiryTime;
        }

        public boolean isExpired() {
            return duration != 0 && expiryTime < System.currentTimeMillis();
        }

        public String getFulfillmentID() {
            return fulfillmentID;
        }

        public String toString() {
            return "Fulfillment Task: Task Id = " + fulfillmentID +
                    ", Bundle Id = " + bundleDescriptor.getBundleID() +
                    ", Adapter Name = " + adapterName;
        }
    }
}
